package org.nicocube.airain.utils.telnet;

import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.util.Arrays;
import java.util.HashMap;
import java.util.Map;

import org.nicocube.airain.utils.server.Server;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

public class CommandBinder {
	private static final Logger log = LoggerFactory.getLogger(CommandBinder.class);
	
	private Server server;
	private Map<String,CommandBinding> commands;
	

	protected interface CommandBinding {
		public String getDescription();
		public String execute(Object [] args) throws EndingCommandException;
	}
	
	protected class MethodCommandBinding implements CommandBinding {
		/**
		 * The method to bind the call for
		 */
		private Method m;
		/**
		 * The command description
		 */
		private String d;
		/**
		 * True if the the command end the 
		 */
		private boolean e;
		/**
		 * 
		 * @param m
		 * @param e
		 */
		public MethodCommandBinding(Method m, String d, boolean e) {
			if (log.isDebugEnabled()) log.debug("bind Method:"+m.getName()+",ending:"+e);
			this.m = m;
			this.d = d;
			this.e = e;
		}
		public String execute(Object [] args) throws EndingCommandException {
			try {
				if (log.isDebugEnabled()) log.debug("invoke Method:"+m.getName()+",ending:"+e);
				Object res = m.invoke(server, args);
				if (e) {
					log.info("Application is to be ending");
					throw new EndingCommandException();
				}
				return res!=null?res.toString():null;
			} catch (IllegalArgumentException e) {
				throw new IllegalStateException("",e);
			} catch (IllegalAccessException e) {
				throw new IllegalStateException("",e);
			} catch (InvocationTargetException e) {
				throw new IllegalStateException("",e);
			}
		}
		@Override
		public String getDescription() {
			return d;
		}
	}
	
	protected class HelpCommandBinding implements CommandBinding {
		private CommandBinder binder;

		public HelpCommandBinding(CommandBinder binder) {
			this.binder = binder;
		}

		@Override
		public String execute(Object[] args) throws EndingCommandException {
			StringBuilder sb = new StringBuilder();
			for (Map.Entry<String, CommandBinding> cmd : binder.commands.entrySet()) {
				sb.append(String.format(
						"%s\t%s\n",
						cmd.getKey(),
						cmd.getValue().getDescription()));
			}
			if (log.isDebugEnabled()) log.debug(sb.toString());
			return sb.toString();
		}

		@Override
		public String getDescription() {
			return "Provide this help.";
		}
		
	}
	
	public CommandBinder(Server server) {
		this.server = server;
		commands = new HashMap<String,CommandBinding>();
		commands.put("help", new HelpCommandBinding(this));
		for (Method m: server.getClass().getDeclaredMethods()) {
			EndingCommand e = m.getAnnotation(EndingCommand.class);
			Command c = m.getAnnotation(Command.class);
			CommandDescription cd = m.getAnnotation(CommandDescription.class);
			if (c != null) {
				log.debug(m.toGenericString());
				log.debug(Arrays.deepToString(m.getAnnotations()));
				commands.put(c.value(),new MethodCommandBinding(m, cd!=null?cd.value():"",e!=null));
			}
		}
	}

	public String process(String command) throws UnknowCommandException, EndingCommandException {
		if (log.isDebugEnabled()) log.debug("Treat command : "+command);
		String[] c = command.split(" ");
		if (commands.containsKey(c[0])) {
			return commands.get(c[0]).execute(Arrays.copyOfRange(c, 1, c.length));
		} else {
			throw new UnknowCommandException("Unknow command :"+c[0]);
		}
	}
}
